package plus.exp;

import java.util.*;

import plus.Function;
import plus.Gamma;
import plus.Gripe;
import plus.Hypo;
import plus.Identifier;
import plus.S_Function;
import plus.Symbol;
import plus.Type;
import plus.Uni;

public final class E_Call extends Exp {
	public static E_Call binary(Exp lhs, String op, Exp rhs, int lineNr) {
		return new E_Call(new Identifier(lineNr, op, null), new Exp[] {lhs, rhs});
	}

	public static E_Call unary(String op, Exp rhs, int lineNr) {
		return new E_Call(new Identifier(lineNr, op, null), new Exp[] {rhs});
	}
	
	private LinkedList<Symbol<Function>> candidates;
	Function solution;
	Identifier fn;
	Exp[] es;
	S_Function arrow;
	
	public boolean solve() {
		if (isSolved()) return true;
		narrowDownCandidates();
		switch (candidates.size()) {
		case 0: setGripeContext(); throw new Gripe("No suitable function found: "+this);
		case 1: adoptSolution(); return true;
		default: furtherSpecifyArgumentTypes(); return false;
		}
	}
	public E_Call(Identifier fn, List<Exp> es) {
		this.fn = fn;
		int arity = es.size();
		this.es = new Exp[arity];
		int i = 0;
		for (Exp e:es) this.es[i++] = e;
	}
	E_Call(Identifier fn, Exp[] es) {
		this.fn = fn;
		this.es = es;
	}
	public boolean isSolved() { return solution != null; }
	public void runConformance() { arrow.conform_to(solution.arrow(), new Gamma()); }
	public void report() { System.err.println("\t"+fn); }
	@Override
	public void threadScope() {
		int i = es.length;
		Type[] domain = new Type[i];
		while (i-->0) {
			Exp e = es[i];
			e.threadScope();
			domain[i] = e.type;
		}
		arrow = new S_Function(domain);
		type = arrow.range;
		candidates = new LinkedList<Symbol<Function>>();
		fn.functionCandidates(es.length, candidates);
		Uni.sites.add(this);
		// Better results may come from one round of solving at this point:
		if (solve()) runConformance();
	}
	@Override String aThis() { return "a function call"; }
	@Override String impliedName() { return fn.name; }
	@Override public String toString() {
		StringBuilder sb = new StringBuilder(fn.toString());
		sb.append('(');
		for (Exp e:es) {
			sb.append(e.aThis());
			sb.append(", ");
		}
		if (es.length>0) {
			int l = sb.length();
			sb.delete(l-2, l);
		}
		sb.append(')');
		return sb.toString();
	}
	private void adoptSolution() {
		this.solution = candidates.removeFirst().definition;
		Uni.progress = true;
		candidates = null;
	}
	private void setGripeContext() {
		Gripe.context = fn;
		Gripe.lineNr = fn.lineNr;
	}
	private void furtherSpecifyArgumentTypes() {
		// TODO compute the intersection of the types of the candidates and
		// TODO then run something like a conformance to further specify types
		// TODO around the call site. (This is another type-algebra problem.)
		// In the short run, consider this optional.
	}
	private void narrowDownCandidates() {
		LinkedList<Symbol<Function>> next = new LinkedList<Symbol<Function>>();
		for (Symbol<Function> s:candidates) {
			if (arrow.canConform(s.definition.arrow(), new Hypo())) next.add(s);
			else Uni.progress = true;
		}
		candidates = next;
	}
	@Override
	public void compilePush() { solution.compileCall(es); }
}